/*
Copyright 2024 New Vector Ltd.
Copyright 2017 Vector Creations Ltd
Copyright 2015 OpenMarket Ltd

SPDX-License-Identifier: AGPL-3.0-only
Please see LICENSE in the repository root for full details.
 */

#import "MXK3PID.h"

@import libPhoneNumber_iOS;

@interface MXK3PID ()
{
    MXRestClient *mxRestClient;
    MXHTTPOperation *currentRequest;
}
@property (nonatomic) NSString *clientSecret;
@property (nonatomic) NSUInteger sendAttempt;
@property (nonatomic) NSString *sid;
@property (nonatomic) MXIdentityService *identityService;
@property (nonatomic) NSString *submitUrl;
/**
 HTTP client dedicated to sending MSISDN token to custom URLs.
 */
@property (nonatomic, strong) MXHTTPClient *msisdnSubmissionHttpClient;

@end

@implementation MXK3PID

- (instancetype)initWithMedium:(NSString *)medium andAddress:(NSString *)address
{
    self = [super init];
    if (self)
    {
        _medium = [medium copy];
        _address = [address copy];
        self.clientSecret = [MXTools generateSecret];
    }
    return self;
}

- (void)cancelCurrentRequest
{
    _validationState = MXK3PIDAuthStateUnknown;
    
    [currentRequest cancel];
    currentRequest = nil;
    mxRestClient = nil;
    self.identityService = nil;

    self.sendAttempt = 1;
    self.sid = nil;
    // Removed potential linked userId
    self.userId = nil;
}

- (void)requestValidationTokenWithMatrixRestClient:(MXRestClient*)restClient
                              isDuringRegistration:(BOOL)isDuringRegistration
                                          nextLink:(NSString*)nextLink
                                           success:(void (^)(void))success
                                           failure:(void (^)(NSError *error))failure
{
    // Sanity Check
    if (_validationState != MXK3PIDAuthStateTokenRequested && restClient)
    {
        // Reset if the current state is different than "Unknown"
        if (_validationState != MXK3PIDAuthStateUnknown)
        {
            [self cancelCurrentRequest];
        }
        
        NSString *identityServer = restClient.identityServer;
        if (identityServer)
        {
            // Use same identity server as REST client for validation token submission
            self.identityService = [[MXIdentityService alloc] initWithIdentityServer:identityServer accessToken:nil andHomeserverRestClient:restClient];
        }
        
        if ([self.medium isEqualToString:kMX3PIDMediumEmail])
        {
            _validationState = MXK3PIDAuthStateTokenRequested;
            mxRestClient = restClient;
            
            currentRequest = [mxRestClient requestTokenForEmail:self.address isDuringRegistration:isDuringRegistration clientSecret:self.clientSecret sendAttempt:self.sendAttempt nextLink:nextLink success:^(NSString *sid) {
                
                self->_validationState = MXK3PIDAuthStateTokenReceived;
                self->currentRequest = nil;
                self.sid = sid;
                
                if (success)
                {
                    success();
                }
                
            } failure:^(NSError *error) {
                
                // Return in unknown state
                self->_validationState = MXK3PIDAuthStateUnknown;
                self->currentRequest = nil;
                // Increment attempt counter
                self.sendAttempt++;
                
                if (failure)
                {
                    failure (error);
                }
                
            }];
        }
        else if ([self.medium isEqualToString:kMX3PIDMediumMSISDN])
        {
            _validationState = MXK3PIDAuthStateTokenRequested;
            mxRestClient = restClient;
            
            NSString *phoneNumber = [NSString stringWithFormat:@"+%@", self.address];
            
            currentRequest = [mxRestClient requestTokenForPhoneNumber:phoneNumber isDuringRegistration:isDuringRegistration countryCode:nil clientSecret:self.clientSecret sendAttempt:self.sendAttempt nextLink:nextLink success:^(NSString *sid, NSString *msisdn, NSString *submitUrl) {
                
                self->_validationState = MXK3PIDAuthStateTokenReceived;
                self->currentRequest = nil;
                self.sid = sid;
                self.submitUrl = submitUrl;
                
                if (success)
                {
                    success();
                }
                
            } failure:^(NSError *error) {
                
                // Return in unknown state
                self->_validationState = MXK3PIDAuthStateUnknown;
                self->currentRequest = nil;
                // Increment attempt counter
                self.sendAttempt++;
                
                if (failure)
                {
                    failure (error);
                }
                
            }];
        }
        else
        {
            MXLogDebug(@"[MXK3PID] requestValidationToken: is not supported for this 3PID: %@ (%@)", self.address, self.medium);
        }
    }
    else
    {
        MXLogDebug(@"[MXK3PID] Failed to request validation token for 3PID: %@ (%@), state: %lu", self.address, self.medium, (unsigned long)_validationState);
    }
}

- (void)submitValidationToken:(NSString *)token
                      success:(void (^)(void))success
                      failure:(void (^)(NSError *error))failure
{
    // Sanity Check
    if (_validationState == MXK3PIDAuthStateTokenReceived)
    {
        if (self.submitUrl)
        {
            _validationState = MXK3PIDAuthStateTokenSubmitted;

            currentRequest = [self submitMsisdnTokenOtherUrl:self.submitUrl token:token medium:self.medium clientSecret:self.clientSecret sid:self.sid success:^{

                self->_validationState = MXK3PIDAuthStateAuthenticated;
                self->currentRequest = nil;

                if (success)
                {
                    success();
                }

            } failure:^(NSError *error) {

                // Return in previous state
                self->_validationState = MXK3PIDAuthStateTokenReceived;
                self->currentRequest = nil;

                if (failure)
                {
                    failure (error);
                }

            }];
        }
        else if (self.identityService)
        {
            _validationState = MXK3PIDAuthStateTokenSubmitted;
            
            currentRequest = [self.identityService submit3PIDValidationToken:token medium:self.medium clientSecret:self.clientSecret sid:self.sid success:^{
                
                self->_validationState = MXK3PIDAuthStateAuthenticated;
                self->currentRequest = nil;
                
                if (success)
                {
                    success();
                }
                
            } failure:^(NSError *error) {
                
                // Return in previous state
                self->_validationState = MXK3PIDAuthStateTokenReceived;
                self->currentRequest = nil;
                
                if (failure)
                {
                    failure (error);
                }
                
            }];
        }
        else
        {
            MXLogDebug(@"[MXK3PID] Failed to submit validation token for 3PID: %@ (%@), identity service is not set", self.address, self.medium);
            
            if (failure)
            {
                failure(nil);
            }
        }
    }
    else
    {
        MXLogDebug(@"[MXK3PID] Failed to submit validation token for 3PID: %@ (%@), state: %lu", self.address, self.medium, (unsigned long)_validationState);
        
        if (failure)
        {
            failure(nil);
        }
    }
}

- (MXHTTPOperation *)submitMsisdnTokenOtherUrl:(NSString *)url
                                         token:(NSString*)token
                                        medium:(NSString *)medium
                                  clientSecret:(NSString *)clientSecret
                                           sid:(NSString *)sid
                                       success:(void (^)(void))success
                                       failure:(void (^)(NSError *))failure
{
    NSDictionary *parameters = @{
                                 @"sid": sid,
                                 @"client_secret": clientSecret,
                                 @"token": token
                                 };

    self.msisdnSubmissionHttpClient = [[MXHTTPClient alloc] initWithBaseURL:nil andOnUnrecognizedCertificateBlock:nil];

    MXWeakify(self);
    return [self.msisdnSubmissionHttpClient requestWithMethod:@"POST"
                                                  path:url
                                            parameters:parameters
                                               success:^(NSDictionary *JSONResponse) {
        success();
        MXStrongifyAndReturnIfNil(self);
        self.msisdnSubmissionHttpClient = nil;
    }
                                               failure:^(NSError *error) {
        failure(error);
        MXStrongifyAndReturnIfNil(self);
        self.msisdnSubmissionHttpClient = nil;
    }];

}

- (void)add3PIDToUser:(BOOL)bind
              success:(void (^)(void))success
              failure:(void (^)(NSError *error))failure
{
    if ([self.medium isEqualToString:kMX3PIDMediumEmail] || [self.medium isEqualToString:kMX3PIDMediumMSISDN])
    {
        MXWeakify(self);
        
        currentRequest = [mxRestClient add3PID:self.sid clientSecret:self.clientSecret bind:bind success:^{

            MXStrongifyAndReturnIfNil(self);

            // Update linked userId in 3PID
            self.userId = self->mxRestClient.credentials.userId;
            self->currentRequest = nil;

            if (success)
            {
                success();
            }
            
        } failure:^(NSError *error) {
            
            MXStrongifyAndReturnIfNil(self);
            
            self->currentRequest = nil;

            if (failure)
            {
                failure (error);
            }
            
        }];

        return;
    }
    else
    {
        MXLogDebug(@"[MXK3PID] bindWithUserId: is not supported for this 3PID: %@ (%@)", self.address, self.medium);
    }

    // Here the validation process failed
    if (failure)
    {
        failure (nil);
    }
}

@end
